/**
 * @file qe_trace.c
 * @author Wei.Studio
 * @brief Trace
 * @version 0.1
 * @date 2024-07-09
 * 
 * @copyright Copyright (c) 2024 Wei.Studio
 * 
 */



#include "qe_log.h"
#include "qe_trace.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include <stdio.h>
#if defined(HAVE_STDARG_H)
#include <stdarg.h>
#else
#error "System don't have stdarg.h"
#endif



QELOG_DOMAIN("qe-vtrace");



#define TRACE_MARKS_SIZE            12
#define TRACE_EVENT_BUFFER_SIZE     1024
#define TRACE_SYM_TAB_SIZE          512
#define TRACE_ARG_MAX_SIZE          32
#define TRACE_VERSION_MAJOR         (1)
#define TRACE_VERSION_MINOR         (0)
#define TRACE_VERSION_PATCH         (0)
#define TRACE_VERSION_BUILD(a,b,c)  (((a)<<16) | ((b)<<8) | (c))
#define TRACE_VERSION               TRACE_VERSION_BUILD(TRACE_VERSION_MAJOR,TRACE_VERSION_MINOR,TRACE_VERSION_PATCH)


#define USER_EVENT                  (128)



typedef void * trace_string_t;

typedef struct
{   
    /* symbol table size, =TRACE_SYM_TAB_SIZE  */
    qe_u32 size;

    qe_u32 next_free;

    /* align to multiple of 4 */
    qe_u8 bytes[4 * (((TRACE_SYM_TAB_SIZE)+3)/4)];

    /* checksum table, use to lookup */
    qe_u16 checksum[64];
}trace_symbol_table;

typedef struct
{
    qe_u8 start_marks[TRACE_MARKS_SIZE];

    /* version major.minor.patch */
    qe_u32 version;

    /* filesize sizeof(qe_trace_recorder) */
    qe_u32 filesize;

    qe_u32 num_events;

    qe_u32 max_events;

    qe_u32 next_free;

    /* clock/timer/counter frequency */
    qe_u32 frequency;

    qe_u32 is_full;

    /* 1:recoder has been started, 0:no start */
    qe_u32 active;

    trace_symbol_table symtab;

    /* event data 4-byte records */ 
    qe_u8 event_bytes[(TRACE_EVENT_BUFFER_SIZE) * 4];

    qe_u8 end_marks[TRACE_MARKS_SIZE];
}qe_trace_recorder;

typedef struct 
{
    qe_u8  type;
    qe_u8  pts;
    qe_u16 payload;
} event_tag;


static qe_bool is_initialized = qe_false;
qe_trace_recorder recorder_data;
qe_trace_recorder *recorder = QE_NULL;




static qe_u8 write_int32(qe_ptr buffer, qe_u8 i, qe_u32 value)
{
	qe_assert(buffer != QE_NULL);

	/* A 32 bit value should begin at an even 4-byte address */
	while ((i % 4) != 0)
	{
		if (i >= TRACE_ARG_MAX_SIZE)
		{
			return 255;
		}

		((qe_u8*)buffer)[i] = 0;
		i++;
	}

	if (i + 4 > TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	((qe_u32*)buffer)[i/4] = value;

	return ((qe_u8) (i + 4));
}

static qe_u8 write_int16(qe_ptr buffer, qe_u8 i, qe_u16 value)
{
	qe_assert(buffer != QE_NULL);

	/* Align to multiple of 2 */
	while ((i % 2) != 0)
	{
		if (i >= TRACE_ARG_MAX_SIZE)
		{
			return 255;
		}

		((qe_u8*)buffer)[i] = 0;
		i++;
	}

	if (i + 2 > TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	((qe_u16*)buffer)[i/2] = value;

	return ((qe_u8) (i + 2));
}

static qe_u8 write_float(qe_ptr buffer, qe_u8 i, float value)
{
	qe_assert(buffer != QE_NULL);

	/* A 32 bit value should begin at an even 4-byte address */
	while ((i % 4) != 0)
	{
		if (i >= TRACE_ARG_MAX_SIZE)
		{
			return 255;
		}

		((qe_u8*)buffer)[i] = 0;
		i++;
	}

	if (i + 4 > TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	((float*)buffer)[i/4] = value;

	return i + 4;
}

static qe_u8 write_double(qe_ptr buffer, qe_u8 i, double value)
{
	qe_u32 * dest;
	qe_u32 * src = (qe_u32*)&value;

	qe_assert(buffer != QE_NULL);

	/* The double is written as two 32 bit values, and should begin at an even
	4-byte address (to avoid having to align with 8 byte) */
	while (i % 4 != 0)
	{
		if (i >= TRACE_ARG_MAX_SIZE)
		{
			return 255;
		}

		((qe_u8*)buffer)[i] = 0;
		i++;
	}

	if (i + 8 > TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	dest = &(((qe_u32 *)buffer)[i/4]);

	dest[0] = src[0];
	dest[1] = src[1];

	return i + 8;
}

static qe_u8 write_int8(qe_ptr buffer, qe_u8 i, qe_u8 value)
{
	qe_assert(buffer != QE_NULL);

	if (i >= TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	((qe_u8*)buffer)[i] = value;

	if (i + 1 > TRACE_ARG_MAX_SIZE)
	{
		return 255;
	}

	return ((qe_u8) (i + 1));
}

static qe_u8 event_format_to_buffer(qe_const_str format, va_list args, qe_ptr buffer, qe_u8 offset)
{
    qe_u8 i = offset;
    qe_uint index = 0;
    qe_uint arg_count = 0;

    while (format[index] != '\0') {
        
        if (format[index] != '%') {
            index++;
            if (index == 255) {
                qe_error("too large arguments, max 32 byte allowed!");
                return 0;
            }
            continue;
        }

        if (format[index + 1] == '%') {
            index += 2;
            continue;
        }

        /* found a posible argument here */
        arg_count++;
        index++;

        while ((format[index] >= '0' && format[index] <= '9') ||
               (format[index] == '#') ||
               (format[index] == '.'))
            index;

        /* check end of string */
        if (format[index] == '\0')
            break;

        switch (format[index]) {

        case 'd':
        case 'x':
        case 'X':
        case 'u':
            i = write_int32(buffer, i, (qe_u32)va_arg(args, qe_u32));
            break;

        case 's':
            //i = write_int16(buffer, i, );
            break;

#if (TRACE_FLOAT_SUPPORT)
        case 'f':
            i = write_float(buffer, i, (float)va_arg(args, double));
            break;
#else
        case 'f':
            i = write_int32(buffer, i, (qe_u32)va_arg(args, double));
            break;
#endif

        case 'l':
            index++;
            switch (format[index]) {
#if (TRACE_FLOAT_SUPPORT)
            case 'f':
                i = write_double(buffer, i, (double)va_arg(args, double));
                break;
#else
            case 'f':
                i = write_int32(buffer, i, (qe_u32)va_arg(args, double));
                break;
#endif
            default:
                break;
            }
            break;

        case 'h':
            index++;
            switch (format[index]) {
            case 'd':
                i = write_int16(buffer, i, (qe_u16)va_arg(args, qe_u32));
                break;
            case 'u':
                i = write_int16(buffer, i, (qe_u16)va_arg(args, qe_u32));
                break;
            default:
                break;
            }
            break;

        case 'b':
            index++;
            switch (format[index]) {
            case 'd':
            case 'u':
                i = write_int8(buffer, i, (qe_u8)va_arg(args, qe_u32));
                break;
            default:
                break;
            }
            break;

        default:
            /* unsupport format */
            arg_count--;
            break;
        }

        if (arg_count > 15) {
            qe_error("too large arguments, max 15 allowed!");
            return 0;
        }
    }

    return (qe_u8)(i+3)/4;
}

static trace_string_t trace_open_symbol(qe_const_str name)
{
    qe_u8 crc;
    qe_u8 len;
    qe_u16 ret;

    //get_checksum(name, &crc, &len);

    //ret = symbol_lookup(name, crc, len);
    if (!ret) {
        //ret = symbol_create(name, crc, len);
    }
    return (trace_string_t)ret;
}

qe_ret qe_vtrace_init(void)
{
    if (is_initialized != qe_false) {
        return qe_ok;
    }

    recorder = &recorder_data;

    qe_memset(recorder, 0, sizeof(qe_trace_recorder));

    recorder->version = TRACE_VERSION;
    recorder->filesize = sizeof(qe_trace_recorder);
    recorder->symtab.size = TRACE_SYM_TAB_SIZE;
    recorder->symtab.next_free = 1;

    /* end marks string:'traceend' */
    recorder->end_marks[0]  = 0x74;
    recorder->end_marks[1]  = 0x72;
    recorder->end_marks[2]  = 0x61;
    recorder->end_marks[3]  = 0x63;
    recorder->end_marks[4]  = 0x65;
    recorder->end_marks[5]  = 0x65;
    recorder->end_marks[6]  = 0x6e;
    recorder->end_marks[7]  = 0x64;
    recorder->end_marks[8]  = 0xF1;
    recorder->end_marks[9]  = 0xF2;
    recorder->end_marks[10] = 0xF3;
    recorder->end_marks[11] = 0xF4;
    recorder->end_marks[12] = 0xF5;

    /* start marks string:'tracestart' */
    recorder->end_marks[0]  = 0x74;
    recorder->end_marks[1]  = 0x72;
    recorder->end_marks[2]  = 0x61;
    recorder->end_marks[3]  = 0x63;
    recorder->end_marks[4]  = 0x65;
    recorder->end_marks[5]  = 0x73;
    recorder->end_marks[6]  = 0x74;
    recorder->end_marks[7]  = 0x61;
    recorder->end_marks[8]  = 0x72;
    recorder->end_marks[9]  = 0x74;
    recorder->end_marks[10] = 0xF1;
    recorder->end_marks[11] = 0xF2;
    recorder->end_marks[12] = 0xF3;

    is_initialized = qe_true;

    recorder->active = 1;

    qe_debug("recorder init and active success");

    return qe_ok;
}

qe_ret qe_trace_vprintf(qe_const_str format, va_list args)
{
    qe_uint num;
    event_tag *tag;
    qe_u32 buf[(3 + (TRACE_ARG_MAX_SIZE)) / 4];

    qe_assert(format != QE_NULL);
    qe_assert(recorder->active == 1);

    qe_debug("format:%s", format);

    tag = (event_tag *)buf;
    tag->type = 0;

    num = event_format_to_buffer(format, args, (qe_ptr)buf, 4);
    qe_debug("buffer n:%d", num);
    qe_hexdump_debug(buf, num*4);

    tag->payload = (qe_u16)trace_open_symbol(format);

    if (recorder->next_free + num > recorder->max_events) {
        qe_memset(&recorder->event_bytes[recorder->next_free*4], 0, 
            (recorder->max_events - recorder->next_free)*4);
        recorder->next_free = 0;
        recorder->is_full = 1;
    }

    qe_memcpy(&recorder->event_bytes[recorder->next_free*4], 
        buf, num*4);

	recorder->event_bytes[recorder->next_free*4] =
		(qe_u8)(USER_EVENT + num - 1);

    recorder->next_free += num;
    recorder->num_events += num;

    if (recorder->next_free >= TRACE_EVENT_BUFFER_SIZE) {
        recorder->is_full = 1;
        recorder->next_free = 0;
    }

    return qe_ok;
}

qe_ret qe_vtrace_printf(const char *fmt, ...)
{
    qe_ret ret;
	va_list args;
	va_start(args, fmt);
	ret = qe_trace_vprintf(fmt, args);
	va_end(args);
    return ret;
}